package net.teamclerks.rpgf.character;

import java.util.HashMap;
import java.util.Map;

import net.teamclerks.rpgf.character.stats.Stat;
import net.teamclerks.rpgf.character.statsheet.CharacterEffectStatSheet;
import net.teamclerks.rpgf.character.statsheet.CharacterEquipmentStatSheet;
import net.teamclerks.rpgf.character.statsheet.CharacterStatSheet;
import net.teamclerks.rpgf.inventory.item.equipment.ArmorItem;
import net.teamclerks.rpgf.inventory.item.equipment.WeaponItem;
import net.teamclerks.rpgf.script.director.Director;

/**
 * Character is the data structure which houses all the combat-relevant
 * data pertaining to a particular person in an RPG. If Avatars are the
 * actors of a Scene, then Character represents all the datapoints of
 * those Avatars, though they are not mapped specifically.<br/><br/>
 * While Character acts as a fundamental connection between an Avatar and
 * his data, Character does not have an Avatar and Avatar does not have
 * a Character. Rather, these two classes exist alone and the connection
 * is inferred by the player by the interaction in the game.<br/><br/>
 * Example:<br/>
 * In a battle, an enemy Avatar may run up to a PlayerAvatar, swing its
 * sword, hit the PlayerAvatar, and the controller may put up that 100
 * damage is done above the PlayerAvatar's head, but in reality, that is
 * simply a view of one Character (a MobCharacter) hitting another (probably
 * a PlayerCharacter).
 * @author msmith
 *
 */
public class Character
{
  /// DATA POINTS ///
  protected int id;
  protected int armorClass;
  protected int weaponClass;
  protected String name;
  
  /// CONTROL DATA ///
  protected Director director;
  protected CharacterStatSheet baseCharacterSheet;
  protected CharacterEquipmentStatSheet equipmentSheet;
  protected CharacterEffectStatSheet effectSheet;
  
  /// DENY CONSTRUCTOR ///
  private Character(){}
  
  /// PUBLIC CONSTRUCTOR ///
  public Character(Director director)
  {
    this();
    this.director = director;
    new CharacterStatSheet(director,this);
    new CharacterEquipmentStatSheet(director,this);
    new CharacterEffectStatSheet(director,this);
  }
  
  /// PUBLIC METHODS ///
  /**
   * Gets whether or not the selected ArmorItem can be equipped
   * by this CharacterEquipmentStatSheet. This is different than simply checking
   * whether this Character can equip the type of ArmorItem being
   * passed. CanEquip is more along the lines of "high enough level
   * to equip" or "cannot use this equipment yet" etc.<br/>
   * <br/>
   * Returns <code>true</code> by default.
   */
  protected boolean canEquip(ArmorItem item)
  {
    return true;
  }
  /**
   * Gets whether or not the selected WeaponItem can be equipped
   * by this CharacterEquipmentStatSheet. This is different than simply checking
   * whether this Character can equip the type of WeaponItem being
   * passed. CanEquip is more along the lines of "high enough level
   * to equip" or "cannot use this equipment yet" etc.<br/>
   * <br/>
   * Returns <code>true</code> by default.
   */
  protected boolean canEquip(WeaponItem item)
  {
    return true;
  }
  /**
   * GetStats returns the "when the dust settles" stats
   * of this Character. This method takes everything on
   * the Character into account from equipment to buffs
   * and debuffs.<br/>
   * This method can be overridden for advanced implementations
   * which have more stat modifiers than the basics:<br/>
   * 1) Base Character stats.<br/>
   * 2) Equipment stats.<br/>
   * 3) Status effect stats.
   * @return stats
   */
  public HashMap<Integer,Stat> getStats()
  {
    HashMap<Integer,Stat> stats = new HashMap<Integer,Stat>();
    for(int statSlot: this.baseCharacterSheet.getStats().keySet())
    {
      Stat appliedTo = new Stat(director);
      appliedTo.setValue(this.baseCharacterSheet.getStats().get(statSlot).getValue());
      if(this.equipmentSheet.getStats().keySet().contains(statSlot))
      {
        appliedTo.setValue( appliedTo.getValue() +
          this.equipmentSheet.getStat(statSlot)
        );
      }
      if(this.effectSheet.getStats().keySet().contains(statSlot))
      {
        appliedTo.setValue( appliedTo.getValue() +
          this.effectSheet.getStat(statSlot)
        );
      }
      appliedTo.setName(this.baseCharacterSheet.getStats().get(statSlot).getName());
      stats.put(statSlot,appliedTo);
    }
    return stats;
  }
  
  /// PUBLIC FINAL METHODS ///
  public final Stat getStat(int statSlot)
  {
    return getStats().get(statSlot);
  }
  public final Map<Integer,WeaponItem> getWeapons()
  {
    return this.equipmentSheet.getWeapons();
  }
  public final Map<Integer,ArmorItem> getArmor()
  {
    return this.equipmentSheet.getArmor();
  }
  /**
   * The equip armor method takes in an ArmorItem and handles all
   * the book-keeping associated with it, such as removing the
   * to-be-equipped Item from the player's Inventory, telling the
   * CharacterStatSheet to apply the stats of the to-be-equipped
   * Item, moving any item-previously-equipped back to the player's
   * Inventory, and removing any stats that the item-previously-equipped
   * may have had.
   * @param item
   */
  public final void equipArmor(ArmorItem item)
  {
    if(this.canEquip(item) && this.canEquipArmorClass(item) && 
       director.getInventory().playerPossessesItem(item))
    {
      // Equip the item
      ArmorItem unequipped = this.equipmentSheet.equipArmor(item);
      // Remove the item from the inventory as it's being moved
      // from the inventory to a Character's EquipmentSheet slot.
      this.director.getInventory().loseItem(item);
      
      if(unequipped != null)
      {
        // Put the unequipped item back into the player's inventory.
        this.director.getInventory().acquireItem(unequipped);
      }
    }
  }
  /**
   * Unequips an ArmorItem at a given slot for this Character and
   * places it back into the Director's Inventory if there was an
   * ArmorItem returned (simply does nothing otherwise).
   * @param slot
   */
  public final void unequipArmor(int slot)
  {
    ArmorItem unequipped = this.equipmentSheet.unequipArmor(slot);
    if(unequipped != null)
    {
      this.director.getInventory().acquireItem(unequipped);
    }
  }
  /**
  * The equip weapon method takes in a WeaponItem and handles all
  * the book-keeping associated with it, such as removing the
  * to-be-equipped Item from the player's Inventory, telling the
  * CharacterStatSheet to apply the stats of the to-be-equipped
  * Item, moving any item-previously-equipped back to the player's
  * Inventory, and removing any stats that the item-previously-equipped
  * may have had.
  * @param item
  */
  public final void equipWeapon(WeaponItem item)
  {
    if(this.canEquip(item) && this.canEquipWeaponClass(item) && 
       director.getInventory().playerPossessesItem(item))
    {
      // Equip the item
      WeaponItem unequipped = this.equipmentSheet.equipWeapon(item);
      // Remove the item from the inventory as it's being moved
      // from the inventory to a Character's EquipmentSheet slot.
      this.director.getInventory().loseItem(item);
      
      if(unequipped != null)
      {
        // Put the unequipped item back into the player's inventory.
        this.director.getInventory().acquireItem(unequipped);
      }
    }
  }
  /**
   * Unequips a WeaponItem at a given slot for this Character and
   * places it back into the Director's Inventory if there was an
   * WeaponItem returned (simply does nothing otherwise).
   * @param slot
   */
  public final void unequipWeapon(int slot)
  {
    WeaponItem unequipped = this.equipmentSheet.unequipWeapon(slot);
    if(unequipped != null)
    {
      this.director.getInventory().acquireItem(unequipped);
    }
  }
  /**
   * Determines whether the given item can be equipped by this
   * character based on nothing but the armor class of each.
   * @param item
   */
  public final boolean canEquipArmorClass(ArmorItem item)
  {
    return this.armorClass == item.getArmorClass();
  }
  /**
   * Determines whether the given item can be equipped by this
   * character based on nothing but the weapon class of each.
   * @param item
   */
  public final boolean canEquipWeaponClass(WeaponItem item)
  {
    return this.weaponClass == item.getWeaponClass();
  }
  /**
   * @return id
   */
  public final int getId()
  {
    return id;
  }
  /**
   * @param id
   */
  public final void setId(int id)
  {
    this.id = id;
  }
  /**
   * @return characterSheet
   */
  public final CharacterStatSheet getBaseCharacterSheet()
  {
    return baseCharacterSheet;
  }
  /**
   * @param characterSheet
   */
  public final void setBaseCharacterSheet(CharacterStatSheet characterSheet)
  {
    this.baseCharacterSheet = characterSheet;
  }
  /**
   * @return equipmentSheet
   */
  public final CharacterEquipmentStatSheet getEquipmentStatSheet()
  {
    return this.equipmentSheet;
  }
  /**
   * @param equipmentSheet
   */
  public final void setEquipmentStatSheet(CharacterEquipmentStatSheet equipmentSheet)
  {
    this.equipmentSheet = equipmentSheet;
  }
  /**
   * @return effectSheet
   */
  public final CharacterEffectStatSheet getCharacterEffectStatSheet()
  {
    return this.effectSheet;
  }
  /**
   * @param effectSheet
   */
  public final void setCharacterEffectStatSheet(CharacterEffectStatSheet effectSheet)
  {
    this.effectSheet = effectSheet;
  }
  /**
   * Gets the armorClass of this Character.
   * @return armorClass
   */
  public final int getArmorClass()
  {
    return this.armorClass;
  }
  /**
   * Sets the armorClass of this Character.
   * @param armorClass
   */
  public final void setArmorClass(int armorClass)
  {
    this.armorClass = armorClass;
  }
  /**
   * @return the weaponClass
   */
  public final int getWeaponClass()
  {
    return weaponClass;
  }
  /**
   * @param weaponClass the weaponClass to set
   */
  public final void setWeaponClass(int weaponClass)
  {
    this.weaponClass = weaponClass;
  }
  /**
   * @return the name
   */
  public final String getName()
  {
    return name;
  }
  /**
   * @param name the name to set
   */
  public final void setName(String name)
  {
    this.name = name;
  }
}
